package edu.gmu.naming;

import edu.gmu.messages.EndPoint;
import edu.gmu.messages.Host;
import edu.gmu.messages.ListManagerRequest;
import edu.gmu.messages.ListManagerResponse;
import edu.gmu.messages.ResourceRequest;
import edu.gmu.messages.ResourceResponse;
import edu.gmu.messages.Topic;
import edu.gmu.middleware.RemoteStub;
import edu.gmu.middleware.Message;
import edu.gmu.middleware.Sink;
import edu.gmu.middleware.SinkFactory;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class NamingService
{
    public static final int WELL_KNOWN_NAMING_PORT = 42000;
    
    private ServerSocket serverSocket = null;
    private int port = 0;
    
    private volatile boolean stopped = false;
    
    private Host[] resourceHostMap = new Host[32];
    private ArrayList<Host> hosts = new ArrayList<Host>();
    
    //private List<EndPoint> nameServers = new ArrayList<EndPoint>();
    
    /**
     * Creates a new NamingService with specified params.
     */
    public NamingService( )
    {
        this.port = WELL_KNOWN_NAMING_PORT;
        
        /*
         * Static list of CalendarManager's endpoints (may need to be dynamic)
         */
        hosts.add( makeHost( "zeus.ite.gmu.edu",2011,"hercules.ite.gmu.edu",2011 ) );
        hosts.add( makeHost( "localhost",2021,"localhost",3021 ) );
        hosts.add( makeHost( "localhost",2031,"localhost",3031 ) );
        hosts.add( makeHost( "localhost",2041,"localhost",3041 ) );
        hosts.add( makeHost( "localhost",2051,"localhost",3051 ) );
        
        
        /*
         * A priori init - this essentially distributes resources over hosts
         */
        int hostsPerBucket = resourceHostMap.length / this.hosts.size();
        //System.out.println("hostsPerBucket="+hostsPerBucket);
        for (int i = 0; i < resourceHostMap.length; i++)
        {
            int index = i/hostsPerBucket;
            // Last one gets remainders
            if( index >= this.hosts.size() )
            {
                index = this.hosts.size()-1;
            }
            //System.out.println("i="+i + " index="+index);
            Host host = this.hosts.get( index );
            resourceHostMap[i] = host;
        }
        
        for (int i = 0; i < resourceHostMap.length; i++)
        {
            Host host = resourceHostMap[i];
            System.out.println( "["+i+"] = "+host );
        }
    }
    
    /**
     * Internal utility to make hosts
     * @return
     */
    public Host makeHost( String primaryAddress, int primaryPort, String replicaAddress, int replicaPort )
    {
        EndPoint primary = new EndPoint( primaryAddress, primaryPort );
        EndPoint replica = new EndPoint( replicaAddress, replicaPort );
        Host host = new Host( primary, replica );
        return host;
    }
    
//    /**
//     * Utility to get hash value from resource name.
//     * @param resource
//     * @return hash
//     */
//    public int hashCode( String resource )
//    {
//        // We get normal 31-bit hash and eliminate upper 32 bits
//        // Thus we end up with value 0-65535
//        int hashCode = resource.hashCode();
//        return hashCode & 0x0000ffff;
//    }
    
    /**
     * Utility to get index of map - essentially a hash function over map.size.
     * 
     * Following is excellent site reference hashCode
     * http://www.javamex.com/tutorials/collections/hash_function_technical_2.shtml
     * @param resource
     * @return index between 0 - (resourceHostMap.length-1)
     */
    public int getBucket( String resource )
    {
        int hashCode = resource.hashCode();
        // Just take lower 5 bits - i guess the same as %, not really but fine
        hashCode = hashCode % resourceHostMap.length;
        return hashCode;
    }
    
    /**
     * Utility to get host from resource name
     * @param resource
     * @return host
     */
    public Host getHost( String resource )
    {
        int index = this.getBucket(resource);
        Host host = this.resourceHostMap[index];
        //System.out.println( "resource "+resource+" maps to index "+index );
        return host;
    }
    
    /**
     * Starts the server listening on specified port - blocks caller's thread
     * until finished.
     * @param int port
     * @throws java.io.IOException
     */
    public void listenRequests( ) throws IOException
    {
        this.serverSocket = new ServerSocket(this.port);
        
        this.stopped = false;
        while( stopped == false )
        {
            // Blocks until client connects
            final Socket clientSocket = this.serverSocket.accept();
            
            System.out.println("TCPEchoServer - Client connected.");
            /*
             * Handles all request in one thread - so make haste!
             */
            handleClient(clientSocket);
        }
    }
    
    /**
     * Handles a new client connection.
     * @param Socket clientSocket
     */
    public void handleClient( Socket clientSocket )
    {
        //--------------------------------------------------------------------//
        // Executed in handlerThread
        //--------------------------------------------------------------------//
        try
        {
            
            Sink sink = SinkFactory.makeTCPSink(clientSocket );
            RemoteStub remoteStub = new RemoteStub( sink, 999, "NamingServiceStub" );
            
            remoteStub.register(Topic.LIST_MANAGER_REQUEST, ListManagerRequest.class);
            remoteStub.register(Topic.RESOURCE_REQUEST, ResourceRequest.class);
            // This is adding to shared list from this thread - need to protect
            
            Message message = remoteStub.receive();
            if( message instanceof ListManagerRequest )
            {
                ListManagerRequest request = (ListManagerRequest)message;
                
                ListManagerResponse response = new ListManagerResponse();
                response.request = request;
                response.hosts   = this.hosts;

                remoteStub.send(Topic.LIST_MANAGER_RESPONSE, response);
            }
            else if( message instanceof ResourceRequest )
            {
                ResourceRequest request = (ResourceRequest)message;

                Host host = this.getHost( request.resource );
                
                ResourceResponse response = new ResourceResponse();
                response.request = request;
                response.host    = host;

                remoteStub.send(Topic.RESOURCE_RESPONSE, response);
            }
            else
            {
                System.out.println("Unknown message received "+message);
            }
            remoteStub.stop();
        }
        catch(IOException ioe)
        {
            ioe.printStackTrace();
        }
        //--------------------------------------------------------------------//
    }
    
    /**
     * Stops the server from listening
     * @throws java.io.IOException
     */
    public void stop()
    {
        this.stopped = true;
        // Stop server - so that we accept no more clients
        if( this.serverSocket != null )
        {
            try
            {
                this.serverSocket.close();
            }
            catch(IOException ioe)
            {
                // ignore
            }
        }
    }
    
    
    /**
     * Starts the naming serice on the localhost+well-known-port
     * @param args
     * @throws java.io.IOException
     */
    public static void main( String[] args ) throws IOException
    {
        NamingService server = new NamingService();
        
        //System.out.println( server.getHost("Alice") );
        //System.out.println( server.getHost("Bob") );
        //System.out.println( server.getHost("Tom") );
        
        server.listenRequests();
    }
    

}
